package log

import (
	"fmt"
	"runtime/debug"
	"sync"
	"time"
)

var globalStore = newStore(5, 32)

// 支持刷堆策略
// 每n秒 每m次写入 执行刷堆  (推荐 n=5   m=32)
//
type store struct {
	nsec   int // 刷堆策略，每n秒
	mwrite int // 刷堆策略,每m次写入

	infos []string // 存放本地日志
	idx   int64    // 每次写入时，序列+1

	lastPopAt time.Time // 上次刷堆时间
	l         *sync.RWMutex

	flush func(jsonline []string) error // 刷堆处理逻辑

	stopping bool // 对服务优雅启停窗口期，标记正在结束中。结束中的所有写入，立刻flush
	stopl    *sync.RWMutex
}

// 刷堆策略, 每nsec秒，mwrite次写入任意条件达成时，执行一次s.flushonetime
func newStore(nsec int, mwrite int) *store {
	if mwrite == 0 {
		mwrite = 32
	}

	if nsec == 0 {
		nsec = 5
	}

	return &store{
		nsec:   nsec,
		mwrite: mwrite,
		infos:  make([]string, 0, 10),
		l:      &sync.RWMutex{},
		flush:  nil,

		stopl: &sync.RWMutex{},
	}
}


// 设置刷堆方法
func (s *store) SetFlush(f func(jsonlines []string)error) {
	s.flush = f
}

func (s *store) write(jsonline string) error {
	var isstoping bool
	s.stopl.RLock()
	isstoping = s.stopping
	s.stopl.RUnlock()

	if isstoping {
		go func() {
			defer func() {
				if e := recover(); e != nil {
					fmt.Printf("recover from %v stack %s", e, debug.Stack())
				}
			}()

			s.flushOneTime([]string{jsonline})
		}()



		return nil
	}

	var shouldFlush bool
	var flushs []string

	s.l.Lock()

	s.infos = append(s.infos, jsonline)

	s.idx ++

	shouldFlush = s.shouldFlush()

	if shouldFlush {
		flushs = make([]string, 0, 10)

		flushs = append(flushs, s.infos...)

		s.infos = []string{}
	}

	s.l.Unlock()

	go func() {
		defer func() {
			if e := recover(); e != nil {
				fmt.Printf("recover from %v stack %s", e, debug.Stack())
			}
		}()

		s.flushOneTime(flushs)
	}()

	return nil

}

func (s *store) shouldFlush() bool {

	if s.idx%int64(s.mwrite) == 0 {
		return true
	}

	if time.Now().Sub(s.lastPopAt).Seconds() > float64(s.nsec) {
		return true
	}
	return false
}

func (s store) flushOneTime(jsonlines []string) error {
	if s.flush == nil {
		for _, v := range jsonlines {
			fmt.Print(v)
		}
		return nil
	}

	return s.flush(jsonlines)
}

func (s *store) FlushBeforeAppStop() error {
	s.stopl.Lock()
	s.stopping = true
	s.stopl.Unlock()

	return s.flushOneTime(s.infos)
}
